package git.soulbgm.utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * 文件操作类
 *
 * @author SoulBGM
 * @date 2018-07-17 15:58
 */
public class FileUtil {

    public static final String SEPARATOR = "/";

    /**
     * 获得系统的临时目录
     *
     * @return {@link String}
     */
    public static String getTmpDir() {
        return System.getProperty("java.io.tmpdir");
    }

    /**
     * 创建文件夹
     * 如果有则不创建，没有则创建
     *
     * @param folder 文件夹
     * @return boolean
     */
    public static boolean createFolder(String folder) {
        return createFolder(new File(folder));
    }

    /**
     * 创建文件夹
     * 如果有则不创建，没有则创建
     *
     * @param folder 文件夹
     * @return boolean
     */
    public static boolean createFolder(File folder) {
        if (folder == null) {
            return false;
        }
        if (exist(folder)) {
            return true;
        }
        File parentFile = folder.getParentFile();
        boolean flag;
        if (parentFile != null) {
            if (exist(parentFile)) {
                flag = true;
            } else {
                flag = parentFile.mkdirs();
            }
            if (flag) {
                return folder.mkdirs();
            }
        }
        return false;
    }

    /**
     * 文件是否存在
     *
     * @param file 文件
     * @return boolean
     */
    public static boolean exist(File file) {
        if (file != null) {
            return file.exists();
        }
        return false;
    }

    /**
     * 创建文件
     * 如果有则不创建，没有则创建
     *
     * @param file 文件
     * @return boolean
     */
    public static boolean createFile(String file) {
        return createFile(new File(file));
    }

    /**
     * 创建文件
     * 如果有则不创建，没有则创建
     *
     * @param file 文件
     * @return boolean
     */
    public static boolean createFile(File file) {
        if (file == null) {
            return false;
        }
        if (exist(file)) {
            return true;
        }
        File parentFile = file.getParentFile();
        boolean flag;
        if (parentFile != null) {
            if (exist(parentFile)) {
                flag = true;
            } else {
                flag = parentFile.mkdirs();
            }
            try {
                if (flag) {
                    return file.createNewFile();
                }
            } catch (IOException e) {
                return false;
            }
        }
        return false;
    }

    /**
     * 判断file是否是文件
     *
     * @param file 文件
     * @return boolean
     */
    public static boolean isFile(File file) {
        return file != null && file.exists() && file.isFile();
    }

    /**
     * 生成路径
     *
     * @param flag 国旗
     * @param name 的名字
     * @return {@link String}
     */
    public static String createPath(boolean flag, String... name) {
        String beginStr = "";
        if (flag) {
            beginStr = SEPARATOR;
        }
        StringBuilder sb = new StringBuilder(beginStr);
        for (String n : name) {
            sb.append(n).append(SEPARATOR);
        }
        sb.delete(sb.length() - SEPARATOR.length(), sb.length());
        return sb.toString();
    }

    /**
     * 删除文件
     *
     * @param path 路径
     */
    public static void delFile(String... path) throws IOException {
        for (String p : path) {
            del(new File(p));
        }
    }

    /**
     * 删除文件或者文件夹<br>
     * 注意：删除文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * <p>
     * 从5.7.6开始，删除文件使用{@link Files#delete(Path)}代替 {@link File#delete()}<br>
     * 因为前者遇到文件被占用等原因时，抛出异常，而非返回false，异常会指明具体的失败原因。
     * </p>
     *
     * @param file 文件对象
     * @return 成功与否
     * @throws IOException IO异常
     * @see Files#delete(Path)
     */
    public static boolean del(File file) throws IOException {
        if (file == null || !file.exists()) {
            // 如果文件不存在或已被删除，此处返回true表示删除成功
            return true;
        }

        if (file.isDirectory()) {
            // 清空目录下所有文件和目录
            boolean isOk = clean(file);
            if (!isOk) {
                return false;
            }
        }

        // 删除文件或清空后的目录
        final Path path = file.toPath();
        try {
            delFile(path);
        } catch (DirectoryNotEmptyException e) {
            // 遍历清空目录没有成功，此时补充删除一次（可能存在部分软链）
            del(path);
        }

        return true;
    }

    /**
     * 清空文件夹<br>
     * 注意：清空文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param directory 文件夹
     * @return 成功与否
     * @throws IOException IO异常
     */
    public static boolean clean(File directory) throws IOException {
        if (directory == null || !directory.exists() || !directory.isDirectory()) {
            return true;
        }

        final File[] files = directory.listFiles();
        if (null != files) {
            for (File childFile : files) {
                if (!del(childFile)) {
                    // 删除一个出错则本次删除任务失败
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 删除文件或者文件夹，不追踪软链<br>
     * 注意：删除文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param path 文件对象
     * @return 成功与否
     * @throws IOException IO异常
     */
    public static boolean del(Path path) throws IOException {
        if (Files.notExists(path)) {
            return true;
        }

        if (isDirectory(path)) {
            Files.walkFileTree(path, DelVisitor.INSTANCE);
        } else {
            delFile(path);
        }
        return true;
    }


    /**
     * 判断是否为目录，如果file为null，则返回false<br>
     * 此方法不会追踪到软链对应的真实地址，即软链被当作文件
     *
     * @param path {@link Path}
     * @return 如果为目录true
     */
    public static boolean isDirectory(Path path) {
        return isDirectory(path, false);
    }

    /**
     * 判断是否为目录，如果file为null，则返回false
     *
     * @param path          {@link Path}
     * @param isFollowLinks 是否追踪到软链对应的真实地址
     * @return 如果为目录true
     */
    public static boolean isDirectory(Path path, boolean isFollowLinks) {
        if (null == path) {
            return false;
        }
        final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
        return Files.isDirectory(path, options);
    }

    /**
     * 删除文件或空目录，不追踪软链
     *
     * @param path 文件对象
     * @throws IOException IO异常
     */
    public static void delFile(Path path) throws IOException {
        try {
            Files.delete(path);
        } catch (AccessDeniedException e) {
            // 可能遇到只读文件，无法删除.使用 file 方法删除
            if (!path.toFile().delete()) {
                throw e;
            }
        }
    }

}

/**
 * 删除操作的FileVisitor实现，用于递归遍历删除文件夹
 *
 * @author SoulBGM
 */
class DelVisitor extends SimpleFileVisitor<Path> {

    public static DelVisitor INSTANCE = new DelVisitor();

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        Files.delete(file);
        return FileVisitResult.CONTINUE;
    }

    /**
     * 访问目录结束后删除目录，当执行此方法时，子文件或目录都已访问（删除）完毕<br>
     * 理论上当执行到此方法时，目录下已经被清空了
     *
     * @param dir 目录
     * @param e   异常
     * @return {@link FileVisitResult}
     * @throws IOException IO异常
     */
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
        if (e == null) {
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        } else {
            throw e;
        }
    }
}